package com.xinghen.common.core.aspect;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Lists;
import com.xinghen.common.base.GlobalConstant;
import com.xinghen.common.base.model.dto.LoginUser;
import com.xinghen.common.base.model.response.RestResult;
import com.xinghen.common.base.properties.XinghenCloudProperties;
import com.xinghen.common.core.annotation.SaveLog;
import com.xinghen.common.core.log.BusinessLog;
import com.xinghen.common.core.util.RequestUtil;
import eu.bitwalker.useragentutils.UserAgent;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.task.TaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.client.RestTemplate;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * @author xinghen
 * @ClassName: LogAspect
 * @Package: com.xinghen.common.core.aspect
 * @Description 日志注解切面
 * @create 2018-04-13 11:12
 */
@Component
@Aspect
@Slf4j
public class LogAspect {

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private TaskExecutor taskExecutor;

    @Autowired
    private XinghenCloudProperties xinghenCloudProperties;

    private ThreadLocal<Date> timeHolder = new ThreadLocal<>();

    private ObjectMapper objectMapper = new ObjectMapper();

    @Pointcut("@annotation(com.xinghen.common.core.annotation.SaveLog)")
    public void saveLogAspect() {
    }

    @Before("saveLogAspect()")
    public void before() {
        this.timeHolder.set(new Date());
    }

    @AfterReturning(pointcut = "saveLogAspect()", returning = "result")
    public void afterReturning(JoinPoint joinPoint, Object result) {
        if (result instanceof RestResult) {
            RestResult restResult = (RestResult) result;
            if (restResult.getCode() == RestResult.SUCCESS_CODE) {
                saveLog(joinPoint, restResult);
            }
        }
        this.timeHolder.remove();
    }

    /**
     * 记录日志
     *
     * @param joinPoint
     * @param restResult
     */
    private void saveLog(JoinPoint joinPoint, RestResult restResult) {
        SaveLog saveLog = getAnnotation(joinPoint);
        if (Objects.isNull(saveLog)) {
            return;
        }
        Date start = this.timeHolder.get();
        Date end = new Date();
        HttpServletRequest request = RequestUtil.currentRequest();
        LoginUser user = RequestUtil.currentUser();
        UserAgent userAgent = UserAgent.parseUserAgentString(request.getHeader(GlobalConstant.Sys.USER_AGENT));
        BusinessLog businessLog = BusinessLog.builder()
                .className(joinPoint.getTarget().getClass().getName())
                .methodName(joinPoint.getSignature().getName())
                .startTime(start)
                .endTime(end)
                .executeTime(end.getTime() - start.getTime())
                .ip(RequestUtil.getRemoteAddr(request))
                .os(userAgent.getOperatingSystem().getName())
                .browser(userAgent.getBrowser().getName())
                .requestUrl(request.getRequestURI())
                .groupId(user.getGroupId())
                .groupName(user.getGroupName())
                .createTime(end)
                .creator(user.getUsername())
                .creatorId(user.getUserId())
                .lastOperator(user.getUsername())
                .lastOperatorId(user.getUserId())
                .logType(saveLog.logType().getCode())
                .logName(saveLog.logType().getName()).build();
        setRequestAndResponseData(businessLog, saveLog, joinPoint, restResult);
        taskExecutor.execute(() ->
                restTemplate.postForObject(xinghenCloudProperties.getLog().getSaveLogUrl(), businessLog, Object.class));
    }

    /**
     * 保存请求参数和响应结果到业务日志中
     *
     * @param businessLog
     * @param saveLog
     * @param joinPoint
     * @param restResult
     */
    private void setRequestAndResponseData(BusinessLog businessLog, SaveLog saveLog, JoinPoint joinPoint, RestResult restResult) {
        if (saveLog.saveRequestData()) {
            try {
                Object[] args = joinPoint.getArgs();
                if (ArrayUtils.isNotEmpty(args)) {
                    List<Object> params = Lists.newArrayList();
                    for (Object arg : args) {
                        if (arg instanceof HttpServletRequest || arg instanceof HttpServletResponse ||
                                arg instanceof Map || arg instanceof BindingResult) {
                            continue;
                        }
                        params.add(arg);
                    }
                    if (CollectionUtils.isNotEmpty(params)) {
                        businessLog.setRequestData(objectMapper.writeValueAsString(params));
                    }
                }
            } catch (Exception e) {
                log.error("获取请求参数,出现错误={}", e.getMessage(), e);
            }
        }
        if (saveLog.saveResponseData()) {
            try {
                businessLog.setResponseData(objectMapper.writeValueAsString(restResult));
            } catch (Exception e) {
                log.error("获取响应数据,出现错误={}", e.getMessage(), e);
            }
        }
    }

    /**
     * 获取SaveLog注解
     *
     * @param joinPoint
     * @return
     */
    private SaveLog getAnnotation(JoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        return signature.getMethod().getAnnotation(SaveLog.class);
    }

}
